﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ThomasGIS.Geometries
{
    public enum PolygonType
    {
        // 自动识别
        Unknown,
        // 单部件面
        Polygon,
        // 多部件面
        MultiPolygon
    }

    public class Polygon : GeometryBase, IPolygon
    {
        public List<Point> PointList { get; } = new List<Point>();
        public List<int> PartList { get; } = new List<int>();
        public BoundaryBox BoundaryBox { get; set; } = null;
        public PolygonType WktType => GetPartsArea().Select(item => item > 0).ToList().Count > 1 ? PolygonType.MultiPolygon : PolygonType.Polygon;

        public int PointNumber => PointList.Count;
        public int PartNumber => PartList.Count;

        public BoundaryBox CalculateBox()
        {
            if (PointNumber > 0)
            {
                double xMin = PointList.Min(item => item.X);
                double xMax = PointList.Max(item => item.X);
                double yMin = PointList.Min(item => item.Y);
                double yMax = PointList.Max(item => item.Y);
                this.BoundaryBox = new BoundaryBox(xMin, yMin, xMax, yMax);
            }
            else
            {
                this.BoundaryBox = new BoundaryBox(0, 0, 0, 0);
            }

            return this.BoundaryBox;
        }

        public Polygon(List<Point> pointList, List<int> partList, BoundaryBox boundaryBox = null)
        {
            PointList.AddRange(pointList);
            PartList.AddRange(partList);
            BoundaryBox = boundaryBox != null ? new BoundaryBox(boundaryBox) : CalculateBox();
        }

        public Polygon(List<Point> pointList, BoundaryBox boundaryBox = null)
        {
            PartList.Add(0);
            PointList.AddRange(pointList);
            BoundaryBox = boundaryBox != null ? new BoundaryBox(boundaryBox) : CalculateBox();
        }

        public Polygon()
        {
            CalculateBox();
        }

        public Polygon(string wkt, PolygonType type = PolygonType.Unknown)
        {
            if (type == PolygonType.Unknown)
            {
                wkt = wkt.ToUpper();
                string subTypeString = wkt.Substring(0, 7);
                if (subTypeString == "POLYGON")
                {
                    type = PolygonType.Polygon;
                }

                subTypeString = wkt.Substring(0, 12);
                if (subTypeString == "MULTIPOLYGON")
                {
                    type = PolygonType.MultiPolygon;
                }
            }

            string wktContent;

            switch (type)
            {
                case PolygonType.Polygon:
                    wktContent = wkt.Substring(7, wkt.Length - 7);
                    wktContent = wktContent.Trim();
                    wktContent = wktContent.Substring(2, wktContent.Length - 4);
                    string[] gonParts = wktContent.Split(new string[] { "),(", "), (" }, StringSplitOptions.RemoveEmptyEntries);
                    foreach (string onePart in gonParts)
                    {
                        string[] points = onePart.Split(new string[] { ",", ", " }, StringSplitOptions.RemoveEmptyEntries);
                        PartList.Add(PointNumber);
                        foreach (string point in points)
                        {
                            string[] coordinates = point.Split(' ');
                            double x = Convert.ToDouble(coordinates[0]);
                            double y = Convert.ToDouble(coordinates[1]);
                            PointList.Add(new Point(x, y));
                        }
                    }
                    break;
                case PolygonType.MultiPolygon:
                    wktContent = wkt.Substring(12, wkt.Length - 12);
                    wktContent = wktContent.Trim();
                    wktContent = wktContent.Substring(3, wktContent.Length - 6);
                    string[] polygons = wktContent.Split(new string[] { ")),((", ")), ((" }, StringSplitOptions.RemoveEmptyEntries);
                    foreach (string polygon in polygons)
                    {
                        string[] gonParts1 = polygon.Split(new string[] { "),(", "), (" }, StringSplitOptions.RemoveEmptyEntries);
                        foreach (string onePart in gonParts1)
                        {
                            string[] points = onePart.Split(new string[] { ",", ", " }, StringSplitOptions.RemoveEmptyEntries);
                            PartList.Add(PointNumber);
                            foreach (string point in points)
                            {
                                string[] coordinates = point.Split(' ');
                                double x = Convert.ToDouble(coordinates[0]);
                                double y = Convert.ToDouble(coordinates[1]);
                                PointList.Add(new Point(x, y));
                            }
                        }
                    }
                    break;
                default:
                    throw new Exception("无法识别的WKT面状字符串");
            }

            BoundaryBox = CalculateBox();
        }

        private double[] GetPartsArea()
        {
            double[] partsArea = new double[PartNumber];
            for (int i = 0; i < PartNumber; i++)
            {
                if (i == PartNumber - 1)
                {
                    double area = 0;
                    for (int j = PartList[i]; j < PointNumber - 1; j++)
                    {
                        Point sp = PointList[j];
                        Point ep = PointList[j + 1];
                        area += (sp.Y + ep.Y) * (ep.X - sp.X) * 0.5;
                    }
                    partsArea[i] = area;
                }
                else
                {
                    double area = 0;
                    for (int j = PartList[i]; j < PartList[i + 1] - 1; j++)
                    {
                        Point sp = PointList[j];
                        Point ep = PointList[j + 1];
                        area += (sp.Y + ep.Y) * (ep.X - sp.X) * 0.5;
                    }
                    partsArea[i] = area;
                }
            }

            return partsArea;
        }

        public IEnumerable<double> GetArea()
        {
            double[] partsArea = GetPartsArea();
            List<double> result = new List<double>();
            int k = 0;
            while (k < PartNumber)
            {
                if (partsArea[k] > 0)
                {
                    double trueArea = partsArea[k];

                    while (k < PartNumber - 1 && partsArea[k + 1] < 0)
                    {
                        trueArea += partsArea[k + 1];
                        k += 1;
                    }

                    result.Add(trueArea);
                }
            }

            return result;
        }

        public string ExportToWkt()
        {
            StringBuilder wktBuilder = new StringBuilder();
            double[] partAreas = GetPartsArea();
            switch (WktType)
            {
                case PolygonType.Polygon:
                    wktBuilder.Append("POLYGON (");
                    for (int i = 0; i < PartNumber; i++)
                    {
                        StringBuilder innerBuilder = new StringBuilder();
                        innerBuilder.Append("(");
                        if (i < PartNumber - 1)
                        {
                            for (int j = PartList[i]; j < PartList[i + 1]; j++)
                            {
                                string coordinates = $"{PointList[j].X} {PointList[j].Y}";
                                innerBuilder.Append(coordinates);
                                if (j < PartList[i + 1] - 1)
                                {
                                    innerBuilder.Append(",");
                                }
                            }
                        }
                        else
                        {
                            for (int j = PartList[i]; j < PointNumber; j++)
                            {
                                string coordinates = $"{PointList[j].X} {PointList[j].Y}";
                                innerBuilder.Append(coordinates);
                                if (j < PointNumber - 1)
                                {
                                    innerBuilder.Append(",");
                                }
                            }
                        }
                        innerBuilder.Append(")");
                        if (i < PartNumber - 1)
                        {
                            innerBuilder.Append(",");
                        }
                    }
                    wktBuilder.Append(")");
                    break;
                case PolygonType.MultiPolygon:
                    wktBuilder.Append("MULTIPOLYGON (");
                    // 有几个面积是正的就有几个polygon，然后依据正负划分polygon
                    int polygonNumber = GetPartsArea().Select(item => item > 0).ToList().Count;
                    // 每个polygon都要生成一个对象，记录当前处理到的part位置
                    int nowPart = 0;
                    for (int i = 0; i < polygonNumber; i++)
                    {
                        StringBuilder polygonBuilder = new StringBuilder();
                        polygonBuilder.Append("(");
                        // 首先结束部分等于开始部分+1，如果是面积是负数则向后移动
                        int endPart = nowPart + 1;
                        while (endPart < PartNumber && partAreas[endPart] < 0)
                        {
                            endPart += 1;
                        }
                        for (int k = nowPart; k < endPart; k++)
                        {
                            StringBuilder partBuilder = new StringBuilder();
                            partBuilder.Append("(");
                            if (k < PartNumber - 1)
                            {
                                for (int m = PartList[k]; m < PartList[k + 1]; m++)
                                {
                                    string coordinates = $"{PointList[m].X} {PointList[m].Y}";
                                    partBuilder.Append(coordinates);
                                    if (m < PartList[k + 1] - 1)
                                    {
                                        partBuilder.Append(",");
                                    }
                                }
                            }
                            else
                            {
                                for (int m = PartList[k]; m < PointNumber; m++)
                                {
                                    string coordinates = $"{PointList[m].X} {PointList[m].Y}";
                                    partBuilder.Append(coordinates);
                                    if (m < PartList[k + 1] - 1)
                                    {
                                        partBuilder.Append(",");
                                    }
                                }

                            }
                            partBuilder.Append(")");
                            polygonBuilder.Append(partBuilder.ToString());
                            partBuilder.Clear();
                            if (k < endPart - 1)
                            {
                                polygonBuilder.Append(",");
                            }
                        }
                        nowPart = endPart;
                        polygonBuilder.Append(")");
                        wktBuilder.Append(polygonBuilder.ToString());
                        if (i < polygonNumber - 1)
                        {
                            wktBuilder.Append(",");
                        }
                    }
                    wktBuilder.Append(")");
                    break;
                default:
                    throw new Exception("无法识别的类型，输出失败");
            }
            return wktBuilder.ToString();
        }

        public override int ContentLength()
        {
            return 2 + 16 + 2 + 2 + 2 * PartNumber + 8 * PointNumber;
        }

        public override int GetFeatureType()
        {
            return 5;
        }

        public IEnumerable<Point> GetPointEnumerable()
        {
            return PointList.AsEnumerable();
        }

        // 添加一个完整的部件
        public bool AddPart(IEnumerable<Point> pointList)
        {
            this.PartList.Add(PointNumber);
            this.PointList.AddRange(pointList);
            return true;
        }

        public IEnumerable<int> GetPartEnumerable()
        {
            return PartList.AsEnumerable();
        }
    }
}
